/*
Copyright (C) THL A29 Limited, a Tencent company. All rights reserved.

SPDX-License-Identifier: Apache-2.0
*/

package main

import (
	"chainmaker.org/chainmaker/pb-go/v2/common"
	sdk "chainmaker.org/chainmaker/sdk-go/v2"
	"fmt"
	"github.com/360EntSecGroup-Skylar/excelize/v2"
	"github.com/panjf2000/ants"
	"log"
	"strconv"
	"sync"
	"time"
)

const (
	createContractTimeout = 5
	claimVersion          = "1.0.0"

	claimContractName = "evidence"

	claimByteCodePath = "../testdata/claim-docker-demo/evidence.7z"

	sdkConfigPKUser1Path     = "../sdk_configs/sdk_config_pk_user1.yml"
	sdkConfigOrg1Client1Path = "../sdk_configs/sdk_config_org1_client1.yml"
	threadNum                = 1000 // 协程池大小 并发数量
	// loopNum					 = 1 //未生效
	testInvoke               = true
	testQuery                = false
)

// EvidenceData 调用合约的结构体
type EvidenceData struct {
	Id       string
	Hash     string
	Metadata string
}

type GoPoolData struct {
	client        *sdk.ChainClient
	chainCodeData *EvidenceData
	wg            *sync.WaitGroup
}

func main() {
	// fmt.Println("---------1----------")
	var (
		idIndex, hashIndex, metadataIndex int // excel 标题列索引
		exist                             bool
		file                              *excelize.File            // excel文件句柄
		testDataLi                        = make([]EvidenceData, 0) // 测试数据切片
		client                            *sdk.ChainClient          // 区块链sdk客户端
		err                               error
		invokePool, queryPool             *ants.PoolWithFunc // 并发协程池
	)
	// fmt.Println("---------2----------")
	// 读取测试excel文件
	file, err = excelize.OpenFile("../testdataa.xlsx")
	if err != nil {
		log.Fatalln("读取测试数据文件失败, 错误：" + err.Error())
	}
	// fmt.Println("---------3----------")

	// 读取所有sheet页数据
	sheets := file.GetSheetList()
	for _, sheetName := range sheets {
		rowsIterator, err := file.Rows(sheetName)
		if err != nil {
			log.Fatalln("读取测试数据文件失败, 错误：" + err.Error())
		}
		fmt.Println("---------3.1----------")
		rowIndex := 0
		titleMap := make(map[string]int)
		for rowsIterator.Next() {
			rowIndex++
			row, err := rowsIterator.Columns()
			if err != nil {
				log.Fatalln("读取测试数据文件失败, 错误：" + err.Error())
			}
			if len(row) < 3 {
				log.Fatalln("测试文件数据与预期不符")
			}
			// fmt.Println(rowIndex,row)
			// fmt.Println("---------3.2----------")
			// 第一行读取标题列
			if rowIndex == 1 {
				for index, title := range row {
					titleMap[title] = index
				}
				fmt.Println(titleMap)
				idIndex, exist = titleMap["id"]
				// fmt.Println(idIndex, exist)
				if !exist {
					log.Fatalln("测试文件标题列未找到id")
				}
				hashIndex, exist = titleMap["hash"]
				if !exist {
					log.Fatalln("测试文件标题列未找到hash")
				}
				metadataIndex, exist = titleMap["metadata"]
				if !exist {
					log.Fatalln("测试文件标题列未找到metadata")
				}
				continue
			}
			// 读取数据行 校验数据 并组建数据结构体
			id := row[idIndex]
			hash := row[hashIndex]
			metadata := row[metadataIndex]
			if id == "" || hash == "" || metadata == "" {
				log.Fatalln("测试数据不合规，行号：" + strconv.Itoa(rowIndex))
			}
			testDataLi = append(testDataLi, EvidenceData{
				Id:       id,
				Hash:     hash,
				Metadata: metadata,
			})
			fmt.Println(len(testDataLi))
		}
	}
	if len(testDataLi) == 0 {
		log.Fatalln("未获取到有效的测试数据")
	}
	// count := loopNum
	count := len(testDataLi)
	// 初始化客户端
	fmt.Println("初始化客户端")
	client, err = sdk.NewChainClient(
		sdk.WithConfPath(sdkConfigPKUser1Path),
		// sdk.WithConfPath(sdkConfigOrg1Client1Path),
	)
	if err != nil {
		log.Fatalln("创建client失败, 错误:" + err.Error())
	}
	var wg sync.WaitGroup //这里使用等待是为了看出结果，阻塞主线程，防止直接停止
	// 创建一个容量为10的goroutine池
	fmt.Println("--------------创建一个容量为10的goroutine池")
	if testInvoke {
		invokePool, err = ants.NewPoolWithFunc(threadNum, invokeChainCode)
		if err != nil {
			log.Fatalln("创建协程池失败，错误：" + err.Error())
		}
		defer invokePool.Release()
		for i := 0; i < count; i++ {
			wg.Add(1)
			goPoolData := GoPoolData{
				client:        client,
				wg:            &wg,
				chainCodeData: &testDataLi[i],
			}
			// 协程执行存证方法
			_ = invokePool.Invoke(goPoolData)
		}
		// 阻塞所有协程执行完成
		wg.Wait()
	}

	if testQuery {
		queryPool, err = ants.NewPoolWithFunc(threadNum, queryChainCode)
		if err != nil {
			log.Fatalln("创建协程池失败，错误：" + err.Error())
		}
		defer queryPool.Release()
		for i := 0; i < count; i++ {
			wg.Add(1)
			goPoolData := GoPoolData{
				client:        client,
				wg:            &wg,
				chainCodeData: &testDataLi[i],
			}
			// 协程执行查询方法
			_ = queryPool.Invoke(goPoolData)
		}
		wg.Wait()
	}

}

// invokeChainCode 执行存证合约
func invokeChainCode(data interface{}) {
	goPoolData := data.(GoPoolData)
	client := goPoolData.client
	chainCodeData := goPoolData.chainCodeData
	wg := goPoolData.wg
	defer wg.Done()
	kvs := []*common.KeyValuePair{
		{
			Key:   "method",
			Value: []byte("Evidence"),
		},
		{
			Key:   "id",
			Value: []byte(chainCodeData.Id),
		},
		{
			Key:   "hash",
			Value: []byte(chainCodeData.Hash),
		},
		{
			Key:   "metadata",
			Value: []byte(chainCodeData.Metadata),
		},
	}
	startTime := time.Now()
	fmt.Println(kvs)
	err := invokeUserContract(client, claimContractName, "Evidence", "", kvs, true, &common.Limit{GasLimit: 200000})
	endTime := time.Now()
	log.Printf("存证合约执行id:%s,开始时间:%v, 结束时间:%v, 耗时：%v, 错误：%v", chainCodeData.Id, startTime, endTime, endTime.Sub(startTime), err)
	return
}

func queryChainCode(data interface{}) {
	goPoolData := data.(GoPoolData)
	client := goPoolData.client
	chainCodeData := goPoolData.chainCodeData
	wg := goPoolData.wg
	defer wg.Done()
	kvs := []*common.KeyValuePair{
		{
			Key:   "method",
			Value: []byte("FindByHash"),
		},
		{
			Key:   "hash",
			Value: []byte(chainCodeData.Hash),
		},
	}
	startTime := time.Now()
	resp, err := client.QueryContract(claimContractName, "FindByHash", kvs, -1)
	endTime := time.Now()
	log.Printf("查询合约执行hash:%s 结果:%s,开始时间:%v, 结束时间:%v, 耗时：%v, 错误：%v", chainCodeData.Hash, resp.String(), startTime, endTime, endTime.Sub(startTime), err)
	return
}

func invokeUserContract(client *sdk.ChainClient, contractName, method, txId string,
	kvs []*common.KeyValuePair, withSyncResult bool, limit *common.Limit) error {

	fmt.Println(contractName, method, txId, kvs, -1, withSyncResult, limit)
	resp, err := client.InvokeContractWithLimit(contractName, method, txId, kvs, -1, withSyncResult, limit)
	if err != nil {
		return err
	}

	if resp.Code != common.TxStatusCode_SUCCESS {
		return fmt.Errorf("invoke contract failed, [code:%d]/[msg:%s]\n", resp.Code, resp.Message)
	}

	if !withSyncResult {
		fmt.Printf("invoke contract success, resp: [code:%d]/[msg:%s]/[txId:%s]\n", resp.Code, resp.Message, resp.ContractResult.Result)
	} else {
		fmt.Printf("invoke contract success, resp: [code:%d]/[msg:%s]/[contractResult:%s]\n", resp.Code, resp.Message, resp.ContractResult)
	}

	return nil
}
